//
// potion.c
// the Potion!
//
// (c) 2008 why the lucky stiff, the freelance professor
//
#include <stdio.h>

#include "potion.h"
#include "internal.h"
#include "opcodes.h"
#include "khash.h"
#include "table.h"

const char potion_banner[] = "potion " POTION_VERSION
                             " (date='" POTION_DATE "', commit='" POTION_COMMIT
                             "', platform='" POTION_PLATFORM "', jit=%d)\n";
const char potion_version[] = POTION_VERSION;

// TODO hsq 支持输入多行代码？需要语法解析器能返回错误信息，最好提示错误类型为不完整？
//    最简单的方法是 REPL 提供块输入首尾标记？
// TODO hsq 可设置全局变量？这样每行都能利用之前的代码。
const char default_script[] = "repl" PN_EXT_BYTECODE;
const char preluge_module[] = "preluge";

// 在标准输出打印 usage ，对应选项 --help -h 。
static void potion_cmd_usage() {
  printf("usage: potion [options] [script] [arguments]\n"
      "  script: default \e[1;32m%s\e[0m\n"
      "\n"
      "  -P, --no-preluge   run without preluge (default: \e[1;32mmodule preluge\e[0m.)\n"
      "\n"
      "  %s, --bytecode     run with bytecode VM (slower, but cross-platform)\n"
      #if POTION_JIT == 0
      "\e[2;3m"
      #endif
      "  %s, --jit          run with JIT VM (faster, x86-64)\n"
      #if POTION_JIT == 0
      "\e[0m"
      #endif
      "  -c, --compile      compile the script to bytecode\n"
      "\n"
      "  -I, --inspect      print only the return value (default: \e[1;31mNO\e[0m)\n"
      "  -V, --verbose      show bytecode and ast info (default: \e[1;31mNO\e[0m)\n"
      "\n"
      "  -h, --help         show this helpful stuff and exit\n"
      "  -v, --version      show version and exit\n"
      "  -s, --stats        show stats and exit\n"
      "(default: %s)\n",
      default_script,
    #if POTION_JIT == 1
      "-B", "\e[1;32m-J\e[0m", "\e[1;32m-J\e[0m"
    #else
      "\e[1;32m-B\e[0m", "-J", "\e[1;32m-B\e[0m"
    #endif
  );
}

#define GC_STATS_FMT  "(fixed=%d, actual=%d, reserved=%d)"
#define GC_STATS      potion_gc_fixed(P), potion_gc_actual(P), potion_gc_reserved(P)

// 在标准输出打印 索引和对象、元组、表等数据类型的内存尺寸，以及 VM 初始化后的 GC 统计数据，
//    对应选项 --stats -s 。
static void potion_cmd_stats(void *sp) {
  Potion *P = potion_create(sp);
  printf("sizeof(PN=%d, PNObject=%d, PNTuple=%d, PNTuple+1=%d, PNTable=%d)\n",
      (int)sizeof(PN), (int)sizeof(struct PNObject), (int)sizeof(struct PNTuple),
      (int)(sizeof(PN) + sizeof(struct PNTuple)), (int)sizeof(struct PNTable));
  printf("GC " GC_STATS_FMT "\n", GC_STATS);
  potion_destroy(P);
}

// 在标准输出打印 Potion 的构建版本、日期、提交、平台和 jit 等信息，对应选项 --version -v 。
static void potion_cmd_version() {
  printf(potion_banner, POTION_JIT);
}

#if 1
static void show_verbose(Potion *P, int verbose, PN val) {
  if (verbose > 1)
    printf("\n-- %s returned %lx " GC_STATS_FMT " --\n",
      (P->jit_on ? "jit" : "vm"), val, GC_STATS);
  if (verbose) {
    STR_AND_PRINT(val);
    printf("\n");
  }
}
#else
#define show_verbose(P, verbose, val) { \
  if (verbose > 1) \
    printf("\n-- %s returned %lx " GC_STATS_FMT " --\n", \
      (P->jit_on ? "jit" : "vm"), val, GC_STATS); \
  if (verbose) { \
    STR_AND_PRINT(val); \
    printf("\n"); \
  } \
}
#endif

// 将 PNProto code dump 到 *.pnb 文件。
static void potion_cmd_dump(Potion *P, PN code) {
  const char * filename = PN_STR_PTR(PN_PROTO(code)->source);
  char pnbpath[strlen(filename) + 8];
  FILE *pnb = NULL;
  sprintf(pnbpath, "%sb", filename);
  pnb = fopen(pnbpath, "wb");
  if (!pnb) {
    FAIL_MSG("could not open %s for writing. check permissions.", pnbpath);
    return;
  }

  code = potion_source_dump(P, code);
  if (fwrite(PN_STR_PTR(code), 1, PN_STR_LEN(code), pnb) != PN_STR_LEN(code)) {
    FAIL_MSG("could not write all bytecode.");
  } else {
    printf("** compiled code saved to %s\n", pnbpath);
    // printf("** run it with: potion %s\n", pnbpath);
  }
  fclose(pnb);
}

// #define SCAN_MEM

#ifdef SCAN_MEM
  static void potion_scan_mem(Potion *P);
#endif

// 如有选项或参数，则根据选项处理程序文件；否则进入 REPL （脚本实现）。
int main(int argc, char *argv[]) {
  POTION_INIT_STACK(sp);
  int verbose = 0,           // verbose: 0-, 1-inspect, 2-verbose
      exec = 1 + POTION_JIT, // exec:    0-compile, 1-bytecode, 2-jit
      preluge = 1;
  const char *script = default_script;

  // if (argc > 1) {
    int i = 1; // argv[0] 是 Potion 程序本身
    for (; i < argc; i++) {
      if (strcmp(argv[i], "-v") == 0 ||
          strcmp(argv[i], "--version") == 0) {
        return (potion_cmd_version(), 0);
      }
      if (strcmp(argv[i], "-h") == 0 ||
          strcmp(argv[i], "--help") == 0) {
        return (potion_cmd_usage(), 0);
      }
      if (strcmp(argv[i], "-s") == 0 ||
          strcmp(argv[i], "--stats") == 0) {
        return (potion_cmd_stats(sp), 0);
      }

      if (strcmp(argv[i], "-P") == 0 ||
          strcmp(argv[i], "--no-preluge") == 0) {
        preluge = 0;
      }

      else
      if (strcmp(argv[i], "-I") == 0 ||
          strcmp(argv[i], "--inspect") == 0) {
        verbose = 1;
      } else if (strcmp(argv[i], "-V") == 0 ||
          strcmp(argv[i], "--verbose") == 0) {
        verbose = 2;
      }

      else
      if (strcmp(argv[i], "-c") == 0 ||
          strcmp(argv[i], "--compile") == 0) {
        exec = 0;
      } else if (strcmp(argv[i], "-B") == 0 ||
          strcmp(argv[i], "--bytecode") == 0) {
        exec = 1;
      } else if (strcmp(argv[i], "-J") == 0 ||
          strcmp(argv[i], "--jit") == 0) {
        #if POTION_JIT == 0
          FAIL_MSG("potion built without JIT support\n");
          return 1;
        #endif
        exec = 2;
      } else {
        script = argv[i++];
        break;
      }
    }

    Potion *P = potion_create(sp);
    #if POTION_JIT == 1
      if (exec) {
        // P->jit_on = exec > 1;
        potion_jit_on(P, PN_NIL, P->lobby, PN_BOOL(exec > 1));
      }
    #endif

    // 命令行参数
    PN args = PN_NIL;
    if (argc > i) {
      args = PN_TUP0();
      for (; i < argc; i++) {
        PN_PUSH(args, PN_STRING(argv[i]));
      }
    }
    PN_DEF(P->lobby, "argv", args);

    if (preluge) {
      // load_module(P, preluge_module, sizeof preluge_module - 1);
      // potion_send(P->lobby, PN_LITERAL("load"), PN_STRING(preluge_module));
      potion_send(PN_STRING(preluge_module), PN_LITERAL("load"));
    }

    PN code = load_or_compile(P, script);
    if (code == PN_NIL) {
      code = ERROR("file %s does not exist.", script);
    }
    if (PN_IS_ERROR(code)) {
      STR_AND_PRINT(code);
      code = PN_NIL;
    }
    if (code == PN_NIL) exit(EXIT_FAILURE);

    if (verbose > 1) {
      if (IS_LOADED(PN_PROTO(code))) {
        printf("\n\n-- loaded --\n");
      } else {
        printf("\n-- parsed --\n");
        // STR_AND_PRINT(PN_PROTO(code)->tree);
        STR_AND_PRINT(potion_send(code, PN_LITERAL("tree")));
        printf("\n");

        printf("\n-- compiled --\n");
      }
      // 打印字节码
      STR_AND_PRINT(code);
      printf("\n");
    }

    // 解释或 jit 执行；根据命令行选项展示运行结果和 GC 统计数据等。
    if (exec) {
      // TODO hsq JIT 打开 verbose 选项时，输出机器码，可指定输出文件，或单独指定选项开关？
      PN val = potion_run(P, code);
      show_verbose(P, verbose, val);
    }
    // 编译，dump 并写入 *.pnb 文件
    else {
      potion_cmd_dump(P, code);
    }

    #ifdef SCAN_MEM
      potion_scan_mem(P);
    #endif

    // TODO hsq potion 解释器未处理信号。
    potion_destroy(P);

    return 0;
  // }

  // Potion *P = potion_create(sp);
  // PN buf = read_file(P, default_script, 0);
  // // 将 repl 代码嵌入此处，需要每行末位加 "\n" ，并转义双引号和反斜线，然后传给 potion_bytes_str() 。
  // potion_eval(P, buf);
  // potion_destroy(P);
  // return 0;
}

#ifdef SCAN_MEM
// TODO hsq 以后了解
static void potion_scan_mem(Potion *P) {
  void *scanptr = (void *)((char *)P->mem->old_lo + (sizeof(PN) * 2));
  while ((PN)scanptr < (PN)P->mem->old_cur) {
        printf("%p.vt = %x (%u)\n",
          scanptr, ((struct PNObject *)scanptr)->vt,
          potion_type_size(P, scanptr));
    if (((struct PNFwd *)scanptr)->fwd != POTION_FWD && ((struct PNFwd *)scanptr)->fwd != POTION_COPIED) {
      if (((struct PNObject *)scanptr)->vt < 0 || ((struct PNObject *)scanptr)->vt > PN_TBUILTIN_END) {
        printf("wrong type for allocated object: %p.vt = %x\n",
          scanptr, ((struct PNObject *)scanptr)->vt);
        break;
      }
    }
    scanptr = (void *)((char *)scanptr + potion_type_size(P, scanptr));
    if ((PN)scanptr > (PN)P->mem->old_cur) {
      printf("allocated object goes beyond GC pointer\n");
      break;
    }
  }
}
#endif
